This notebook looks at cell type annotations and gene set expression across all samples in SCPCP00015 and assigns “final” annotations. To do this we are using the merged, but not batch-corrected, object containing the gene expression data for all samples.

The following input is used:

Setup

suppressPackageStartupMessages({
  # load required packages
  library(SingleCellExperiment)
  library(ggplot2)
})

# Set default ggplot theme
theme_set(
  theme_classic()
)

# set seed
set.seed(2024)

# quiet messages
options(readr.show_col_types = FALSE)
ComplexHeatmap::ht_opt(message = FALSE)
# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)

# The current data directory, found within the repository base directory
data_dir <- file.path(repository_base, "data", "current", "results", "merge-sce", "SCPCP000015")

# The path to this module
module_base <- file.path(repository_base, "analyses", "cell-type-ewings") 
# path to sce 
sce_file <- file.path(data_dir, "SCPCP000015_merged.rds")

# path to workflow results
workflow_results_dir <- file.path(module_base, "results")

singler_results_dir <- file.path(workflow_results_dir, "aucell_singler_annotation")
singler_results_files <- list.files(singler_results_dir, pattern = "*singler-classifications.tsv", full.names = TRUE, recursive = TRUE)
library_ids <- stringr::str_remove(basename(singler_results_files), "_singler-classifications.tsv")


aucell_results_dir <- file.path(workflow_results_dir, "aucell-ews-signatures")
aucell_results_file <- file.path(aucell_results_dir, "SCPCP000015_auc-ews-gene-signatures.tsv")

consensus_results_dir <- file.path(repository_base, "analyses", "cell-type-consensus", "results", "cell-type-consensus", "SCPCP000015")
consensus_results_files <- list.files(consensus_results_dir, pattern = "*_consensus-cell-type-assignments.tsv.gz", full.names = TRUE, recursive = TRUE)

# small gene sets
visser_marker_genes_file <- file.path(module_base, "references", "visser-all-marker-genes.tsv")
cell_state_genes_file <- file.path(module_base, "references", "tumor-cell-state-markers.tsv")

# marker genes to be used for validating assignments 
validation_markers_file <- file.path(module_base, "references", "combined-markers.tsv")
# output file to save final annotations 
results_dir <- file.path(module_base, "results", "final-annotations")
output_file <- file.path(results_dir, glue::glue("SCPCP000015_celltype-annotations.tsv.gz"))
# source in setup functions prep_results()
setup_functions <- file.path(module_base, "template_notebooks", "utils", "setup-functions.R")
source(setup_functions)

# source in validation functions 
# calculate_mean_markers(), plot_faceted_umap()
validation_functions <- file.path(module_base, "scripts", "utils", "tumor-validation-helpers.R")
source(validation_functions)

# source in plotting functions 
# expression_umap(), cluster_density_plot(), and annotated_exp_heatmap()
plotting_functions <- file.path(module_base, "template_notebooks", "utils", "plotting-functions.R")
source(plotting_functions)
stopifnot(
  "sce file does not exist" = file.exists(sce_file),
  "aucell results file does not exist" = file.exists(aucell_results_file)
)
# read in sce
sce <- readr::read_rds(sce_file)

# read in workflow results
singler_df <- singler_results_files |> 
  purrr::set_names(library_ids) |>
  purrr::map(readr::read_tsv) |> 
  dplyr::bind_rows(.id = "library_id")

aucell_df <- readr::read_tsv(aucell_results_file) |> 
  tidyr::separate(barcodes, "-", into = c("library_id", "barcodes"))

# consensus cell types 
consensus_df <- consensus_results_files |> 
  purrr::map(readr::read_tsv) |> 
  dplyr::bind_rows()

# read in marker genes and combine into one list 
visser_markers_df <- readr::read_tsv(visser_marker_genes_file) |> 
  dplyr::select(cell_type, ensembl_gene_id, gene_symbol) |> 
  unique()
  
cell_state_markers_df <- readr::read_tsv(cell_state_genes_file) |> 
  dplyr::select(cell_type = cell_state, ensembl_gene_id, gene_symbol)

all_markers_df <- dplyr::bind_rows(list(visser_markers_df, cell_state_markers_df))

Prepare data for plotting

all_results_df <- prep_results(
  sce, 
  singler_df = singler_df, 
  cluster_df = NULL, 
  aucell_df = aucell_df,
  consensus_df = consensus_df,
  cluster_nn = params$cluster_nn,
  cluster_res = params$cluster_res,
  join_columns = c("barcodes", "library_id")
  ) |>
  dplyr::mutate(barcodes = glue::glue("{library_id}-{barcodes}"))
Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
Please use `all_of()` or `any_of()` instead.
# Was:
data %>% select(join_columns)

# Now:
data %>% select(all_of(join_columns))

See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
  
cell_types <- unique(all_markers_df$cell_type)

# get the mean expression of all genes for each cell state
gene_exp_df <- cell_types |>
  purrr::map(\(type){
    calculate_mean_markers(all_markers_df, sce, type, cell_type)
  }) |>
  purrr::reduce(dplyr::inner_join, by = "barcodes")

all_info_df <- all_results_df |> 
  dplyr::left_join(gene_exp_df, by = "barcodes")

Results summary

First, we will just summarize the results from the various inputs and workflows. Then we will look at those results and assign any tumor cells and refine annotations as needed.

aucell-singler-annotation assignments

The below UMAP shows the top cell types assigned by SingleR in the aucell-singler-annotation.sh workflow. The top 7 cell types are shown and all other cell types are grouped together as “All remaining cell types”.

plot_faceted_umap(all_info_df, singler_lumped, legend_title = "SingleR cell types") +
  theme(strip.text = element_text(size = 8))

Consensus cell type assignments

The below UMAP shows the top cell types for which consensus cell types were assigned. Consensus cell types are observed when cell types from SingleR and CellAssign share a common ancestor. If no consensus is found, the cells are labeled with “Unknown”. We anticipate that in many cases, the “Unknown” cells will be tumor cells. The top 7 cell types are shown and all other cell types are grouped together as “All remaining cell types”.

plot_faceted_umap(all_info_df, consensus_lumped, legend_title = "Consensus cell types") +
  theme(strip.text = element_text(size = 8))

AUCell results

The below plots show the AUC values determined by AUCell and output from run-aucell-ews-signatures.sh. The first plot shows the individual AUC values on the UMAP.

For context:

  • Any gene sets labeled with up represent EWS-FLI1 target genes that we expect to be upregulated in EWS-FLI1 high tumor cells.
  • Any gene sets labeled with down represent EWS-FLI1 repressed genes that we expect to be downregulated in EWS-FLI1 high tumor cells and upregulated in EWS-FLI1 low tumor cells.
  • The aynaud-ews-targets is a list of genes found to be upregulated in EWS-FLI1 high tumor cells.
  • The wrenn-nt5e-genes is a list of genes found to be upregulated in EWS-FLI1 low tumor cells/ cancer associated fibroblasts.
  • The gobp_ECM and hallmark_EMT are gene sets that are hypothesized to be upregulated in EWS-FLI1 low tumor cells/ cancer associated fibroblasts.
# get the individual thresholds determined by AUCell for each msigdb geneset
# we want this so we can show the threshold on the density plots 
auc_threshold_df <- all_info_df |> 
  tidyr::pivot_longer(starts_with("threshold_auc_"), names_to = "geneset", values_to = "threshold") |> 
  dplyr::mutate(
    geneset = stringr::str_remove(geneset, "threshold_auc_")
  ) |> 
  dplyr::select(barcodes, geneset, threshold)

# reformat auc data for density plots and UMAPs showing AUC values 
auc_df <- all_info_df |> 
  tidyr::pivot_longer(starts_with("auc_"), names_to = "geneset", values_to = "auc_value") |> 
  dplyr::mutate(
    geneset = stringr::str_remove(geneset, "auc_")
  ) |> 
  dplyr::select(barcodes, UMAP1, UMAP2, geneset, auc_value) |> 
  dplyr::left_join(auc_threshold_df, by = c("barcodes", "geneset")) |> 
  dplyr::mutate(
    in_geneset =  auc_value > threshold
  )
expression_umap(auc_df, auc_value, geneset)

The below plot shows the distribution of AUC values for each gene set colored based on if the AUC value is above the threshold determined by AUCell, indicated with a dotted line.


ggplot(auc_df, aes(x = auc_value, color = in_geneset, fill = in_geneset)) +
  geom_density(alpha = 0.5, bw = 0.01) +
  facet_wrap(vars(geneset)) +
  ggplot2::geom_vline(data = auc_df,
                      mapping = aes(xintercept = threshold),
                      lty = 2) +
  theme(
      aspect.ratio = 1,
      strip.background = element_rect(fill = "transparent", linewidth = 0.5),
      panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5)
    ) 

Here we look at the AUC values for each gene set across all consensus cell types.

auc_columns <- colnames(all_info_df)[which(startsWith(colnames(all_info_df), "auc_"))]
cluster_density_plot(all_info_df, auc_columns, "consensus_lumped", "AUC")

Here we look at the AUC values for each gene set across all SingleR cell types.

cluster_density_plot(all_info_df, auc_columns, "singler_lumped", "AUC")

The heatmap below shows the AUC values for all cells and all gene sets and the final refined cell type annotations.

# sort by final cell types 
all_info_df <- all_info_df |> 
  dplyr::arrange(consensus_lumped)

single_annotation_heatmap(
  all_info_df, 
  exp_columns = auc_columns, 
  cell_type_column = "consensus_lumped", 
  legend_title = "AUC"
)

Mean expression of custom gene sets

Below we look at the mean expression of all genes in each of the custom gene sets we have across the consensus cell types. First using a density plot and then using a heatmap.

mean_exp_columns <- colnames(all_info_df)[which(endsWith(colnames(all_info_df), "_mean"))]
cluster_density_plot(all_info_df, mean_exp_columns, annotation_column = "consensus_lumped", "mean gene expression")

single_annotation_heatmap(
  all_info_df, 
  exp_columns = mean_exp_columns, 
  cell_type_column = "consensus_lumped", 
  legend_title = "AUC"
)

Conclusions based on workflow results

Looking at these results, it looks like things labeled as “tumor” by SingleR and “Unknown” in the consensus cell types have high AUC values for the EWS-FLI1 upregulated gene sets. We also see low expression of EWS-FLI1 repressed targets in these cells. It looks like the cells that are called as muscle cells in both cell types have high expression of EWS-FLI1 targets. This makes me think those are actually tumor cells that are mis-classified.

Intriguingly we see high expression of hallmark_EMT and the EWS-FLI1 repressed targets in fibroblasts, which also happen to have high expression of the EWS-FLI1 low targets identifed by Wrenn et al. (wrenn-nt5e-genes). That paper identifies EWS-FLI1 low cells as cancer associated fibroblasts. It’s also important to note that many of the genes that define the EWS-FLI1 low state are also high in fibroblasts, so distinguishing them may be difficult. We also see that the chondrocytes identified in SingleR have high expression of this gene set.

Another key observation is that we see separation between the AUC values observed in tumor/ other cells in the density plots, but these values are much lower than the AUC threshold automatically determined by AUCell. I think because of that we want to pick gene sets where we see very clear separation in AUC values between cell types to aid in finalizing our assignments.

The density plots also show increased expression in the immune markers in immune cell populations and endothelial cell markers in the endothelial cells. There may be a small group of cells that have high expression of the proliferative markers. Perhaps we can label any cells that are tumor cells and show proliferative markers as Tumor EWS-high proliferative.

Refining cell type annotations

Here we will combine annotations from SingleR and consensus cell types with help from the AUCell output. We’ll use the following criteria:

For plotting purposes, we won’t show any of the cell types that only have a handful of cells. Let’s see what cell types we won’t be looking at if we only plot the top 9 cell types.

lumped_celltypes <- unique(all_info_df$final_lumped)
all_celltypes <- unique(all_info_df$final_annotation)
missing_celltypes <- setdiff(all_celltypes, lumped_celltypes)

missing_only <- all_info_df |> 
  dplyr::filter(final_annotation %in% missing_celltypes)


missing_only |> 
  dplyr::count(final_annotation) |> 
  dplyr::arrange(desc(final_annotation))

I think we can leave these few cells alone and just lump them as “All remaining cell types” for plots as we have been doing.

Validation of cell type assignments

In the following section we’ll show some plots to help validate that we are content with our cell type assignments. These plots will look specifically at expression of a set of key marker genes that we expect to up in the assigned cell types. These markers are found in references/combined-markers.tsv and include a smaller set of markers for each of the cell types we have identified.

validation_markers_df <- readr::read_tsv(validation_markers_file)

# pull out list of genes 
genes <- validation_markers_df |>
  dplyr::pull(ensembl_gene_id)

# get individual gene counts for all marker genes 
gene_cts <- logcounts(sce[genes, ]) |>
  as.matrix() |>
  t() |> 
  as.data.frame()
gene_cts$barcodes <- rownames(gene_cts)

# get all unique cell types from the final lumped group 
celltypes_df <- all_info_df |> 
  dplyr::select(barcodes, final_lumped)

# create a df that has gene expression column and column indicating whether or not the gene is detected in that cell 
genes_df <- gene_cts |> 
  tidyr::pivot_longer(!barcodes, names_to = "ensembl_gene_id", values_to = "gene_exp") |> 
  dplyr::left_join(celltypes_df, by = c("barcodes")) |> 
  dplyr::left_join(validation_markers_df, by = c("ensembl_gene_id")) |> 
  dplyr::rowwise() |> 
  dplyr::mutate(
    detected = gene_exp > 0 # column indicating if gene is present or not
  )

# get total number of cells per final annotation group 
total_cells_df <- genes_df |> 
  dplyr::select(barcodes, final_lumped) |> 
  unique() |> 
  dplyr::count(final_lumped, name = "total_cells")

# get total number of cells each gene is detected in per group 
# and mean gene expression per group 
group_stats_df <- genes_df |> 
  dplyr::group_by(final_lumped, ensembl_gene_id) |>
  dplyr::summarize(
    detected_count = sum(detected),
    mean_exp = mean(gene_exp)
  )
`summarise()` has grouped output by 'final_lumped'. You can override using the `.groups` argument.
gene_summary_df <- genes_df |> 
  # add total cells
  dplyr::left_join(total_cells_df, by = c("final_lumped")) |> 
  # add per gene stats
  dplyr::left_join(group_stats_df, by = c("ensembl_gene_id", "final_lumped")) |> 
  dplyr::select(gene_symbol, final_lumped, cell_type, total_cells, detected_count, mean_exp) |> 
  unique() |>
  dplyr::rowwise() |> 
  dplyr::mutate(
    # get total percent
    percent_exp = (detected_count/total_cells) * 100,
    # order genes based on cell type they indicate
    gene_symbol = factor(gene_symbol, levels = validation_markers_df$gene_symbol),
    final_lumped = forcats::fct_relevel(final_lumped, cell_type_order)
    
  ) 

A few notes from this plot:

Let’s make a heatmap version of the same plot.

And finally we’ll look at our annotations on a UMAP! Because, why not.

Export cell types

Now that we have our cell types let’s export them to a TSV to save for future use!

readr::write_tsv(annotation_df, output_file)
Warning: cannot open compressed file '/Users/allyhawkins/Documents/ALSF/forked_repos/OpenScPCA-analysis/analyses/cell-type-ewings/results/final-annotations/SCPCP000015_celltype-annotations.tsv.gz', probable reason 'No such file or directory'Error in open.connection(3L, "wb") : cannot open the connection

Session info

# record the versions of the packages used in this analysis and other environment information
sessionInfo()
LS0tCnRpdGxlOiAiQ2VsbCB0eXBlIGFzc2lnbm1lbnRzIGZvciBTQ1BDUDAwMDAxNSIKYXV0aG9yOiBBbGx5IEhhd2tpbnMKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIgotLS0KClRoaXMgbm90ZWJvb2sgbG9va3MgYXQgY2VsbCB0eXBlIGFubm90YXRpb25zIGFuZCBnZW5lIHNldCBleHByZXNzaW9uIGFjcm9zcyBhbGwgc2FtcGxlcyBpbiBgU0NQQ1AwMDAxNWAgYW5kIGFzc2lnbnMgImZpbmFsIiBhbm5vdGF0aW9ucy4gClRvIGRvIHRoaXMgd2UgYXJlIHVzaW5nIHRoZSBtZXJnZWQsIGJ1dCBub3QgYmF0Y2gtY29ycmVjdGVkLCBvYmplY3QgY29udGFpbmluZyB0aGUgZ2VuZSBleHByZXNzaW9uIGRhdGEgZm9yIGFsbCBzYW1wbGVzLiAKClRoZSBmb2xsb3dpbmcgaW5wdXQgaXMgdXNlZDogCgotIEFubm90YXRpb25zIG9idGFpbmVkIGJ5IHJ1bm5pbmcgYFNpbmdsZVJgIHdpdGggdHVtb3IgY2VsbHMgYXMgYSByZWZlcmVuY2Ugb3V0cHV0IGJ5IGBhdWNlbGwtc2luZ2xlci1hbm5vdGF0aW9uLnNoYC4gCi0gQ29uc2Vuc3VzIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyBvdXRwdXQgZnJvbSB0aGUgYGNlbGwtdHlwZS1jb25zZW5zdXNgIG1vZHVsZS4gCi0gQVVDIHZhbHVlcyBhcyBjYWxjdWxhdGVkIGJ5IGBBVUNlbGxgIGZvciBhIHNldCBvZiBFd2luZyBzYXJjb21hIHNwZWNpZmljIGdlbmUgc2V0cyBpbiBNU2lnREIgb3V0cHV0IGJ5IGBydW4tYXVjZWxsLWV3cy1zaWduYXR1cmVzLnNoYC4gCgojIyBTZXR1cAoKYGBge3IgcGFja2FnZXN9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgIyBsb2FkIHJlcXVpcmVkIHBhY2thZ2VzCiAgbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKICBsaWJyYXJ5KGdncGxvdDIpCn0pCgojIFNldCBkZWZhdWx0IGdncGxvdCB0aGVtZQp0aGVtZV9zZXQoCiAgdGhlbWVfY2xhc3NpYygpCikKCiMgc2V0IHNlZWQKc2V0LnNlZWQoMjAyNCkKCiMgcXVpZXQgbWVzc2FnZXMKb3B0aW9ucyhyZWFkci5zaG93X2NvbF90eXBlcyA9IEZBTFNFKQpDb21wbGV4SGVhdG1hcDo6aHRfb3B0KG1lc3NhZ2UgPSBGQUxTRSkKYGBgCgoKYGBge3IgYmFzZSBwYXRoc30KIyBUaGUgYmFzZSBwYXRoIGZvciB0aGUgT3BlblNjUENBIHJlcG9zaXRvcnksIGZvdW5kIGJ5IGl0cyAoaGlkZGVuKSAuZ2l0IGRpcmVjdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCiMgVGhlIGN1cnJlbnQgZGF0YSBkaXJlY3RvcnksIGZvdW5kIHdpdGhpbiB0aGUgcmVwb3NpdG9yeSBiYXNlIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IiwgInJlc3VsdHMiLCAibWVyZ2Utc2NlIiwgIlNDUENQMDAwMDE1IikKCiMgVGhlIHBhdGggdG8gdGhpcyBtb2R1bGUKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImNlbGwtdHlwZS1ld2luZ3MiKSAKYGBgCgpgYGB7cn0KIyBwYXRoIHRvIHNjZSAKc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiU0NQQ1AwMDAwMTVfbWVyZ2VkLnJkcyIpCgojIHBhdGggdG8gd29ya2Zsb3cgcmVzdWx0cwp3b3JrZmxvd19yZXN1bHRzX2RpciA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJyZXN1bHRzIikKCnNpbmdsZXJfcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKHdvcmtmbG93X3Jlc3VsdHNfZGlyLCAiYXVjZWxsX3NpbmdsZXJfYW5ub3RhdGlvbiIpCnNpbmdsZXJfcmVzdWx0c19maWxlcyA8LSBsaXN0LmZpbGVzKHNpbmdsZXJfcmVzdWx0c19kaXIsIHBhdHRlcm4gPSAiKnNpbmdsZXItY2xhc3NpZmljYXRpb25zLnRzdiIsIGZ1bGwubmFtZXMgPSBUUlVFLCByZWN1cnNpdmUgPSBUUlVFKQpsaWJyYXJ5X2lkcyA8LSBzdHJpbmdyOjpzdHJfcmVtb3ZlKGJhc2VuYW1lKHNpbmdsZXJfcmVzdWx0c19maWxlcyksICJfc2luZ2xlci1jbGFzc2lmaWNhdGlvbnMudHN2IikKCgphdWNlbGxfcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKHdvcmtmbG93X3Jlc3VsdHNfZGlyLCAiYXVjZWxsLWV3cy1zaWduYXR1cmVzIikKYXVjZWxsX3Jlc3VsdHNfZmlsZSA8LSBmaWxlLnBhdGgoYXVjZWxsX3Jlc3VsdHNfZGlyLCAiU0NQQ1AwMDAwMTVfYXVjLWV3cy1nZW5lLXNpZ25hdHVyZXMudHN2IikKCmNvbnNlbnN1c19yZXN1bHRzX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiYW5hbHlzZXMiLCAiY2VsbC10eXBlLWNvbnNlbnN1cyIsICJyZXN1bHRzIiwgImNlbGwtdHlwZS1jb25zZW5zdXMiLCAiU0NQQ1AwMDAwMTUiKQpjb25zZW5zdXNfcmVzdWx0c19maWxlcyA8LSBsaXN0LmZpbGVzKGNvbnNlbnN1c19yZXN1bHRzX2RpciwgcGF0dGVybiA9ICIqX2NvbnNlbnN1cy1jZWxsLXR5cGUtYXNzaWdubWVudHMudHN2Lmd6IiwgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IFRSVUUpCgojIHNtYWxsIGdlbmUgc2V0cwp2aXNzZXJfbWFya2VyX2dlbmVzX2ZpbGUgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAicmVmZXJlbmNlcyIsICJ2aXNzZXItYWxsLW1hcmtlci1nZW5lcy50c3YiKQpjZWxsX3N0YXRlX2dlbmVzX2ZpbGUgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAicmVmZXJlbmNlcyIsICJ0dW1vci1jZWxsLXN0YXRlLW1hcmtlcnMudHN2IikKCiMgbWFya2VyIGdlbmVzIHRvIGJlIHVzZWQgZm9yIHZhbGlkYXRpbmcgYXNzaWdubWVudHMgCnZhbGlkYXRpb25fbWFya2Vyc19maWxlIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInJlZmVyZW5jZXMiLCAiY29tYmluZWQtdmFsaWRhdGlvbi1tYXJrZXJzLnRzdiIpCmBgYAoKYGBge3J9CiMgb3V0cHV0IGZpbGUgdG8gc2F2ZSBmaW5hbCBhbm5vdGF0aW9ucyAKcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAicmVzdWx0cyIsICJmaW5hbC1hbm5vdGF0aW9ucyIpCmZzOjpkaXJfY3JlYXRlKHJlc3VsdHNfZGlyKQpvdXRwdXRfZmlsZSA8LSBmaWxlLnBhdGgocmVzdWx0c19kaXIsIGdsdWU6OmdsdWUoIlNDUENQMDAwMDE1X2NlbGx0eXBlLWFubm90YXRpb25zLnRzdi5neiIpKQpgYGAKCgpgYGB7cn0KIyBzb3VyY2UgaW4gc2V0dXAgZnVuY3Rpb25zIHByZXBfcmVzdWx0cygpCnNldHVwX2Z1bmN0aW9ucyA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJ0ZW1wbGF0ZV9ub3RlYm9va3MiLCAidXRpbHMiLCAic2V0dXAtZnVuY3Rpb25zLlIiKQpzb3VyY2Uoc2V0dXBfZnVuY3Rpb25zKQoKIyBzb3VyY2UgaW4gdmFsaWRhdGlvbiBmdW5jdGlvbnMgCiMgY2FsY3VsYXRlX21lYW5fbWFya2VycygpLCBwbG90X2ZhY2V0ZWRfdW1hcCgpCnZhbGlkYXRpb25fZnVuY3Rpb25zIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInNjcmlwdHMiLCAidXRpbHMiLCAidHVtb3ItdmFsaWRhdGlvbi1oZWxwZXJzLlIiKQpzb3VyY2UodmFsaWRhdGlvbl9mdW5jdGlvbnMpCgojIHNvdXJjZSBpbiBwbG90dGluZyBmdW5jdGlvbnMgCiMgZXhwcmVzc2lvbl91bWFwKCksIGNsdXN0ZXJfZGVuc2l0eV9wbG90KCksIGFuZCBhbm5vdGF0ZWRfZXhwX2hlYXRtYXAoKQpwbG90dGluZ19mdW5jdGlvbnMgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAidGVtcGxhdGVfbm90ZWJvb2tzIiwgInV0aWxzIiwgInBsb3R0aW5nLWZ1bmN0aW9ucy5SIikKc291cmNlKHBsb3R0aW5nX2Z1bmN0aW9ucykKYGBgCgpgYGB7cn0Kc3RvcGlmbm90KAogICJzY2UgZmlsZSBkb2VzIG5vdCBleGlzdCIgPSBmaWxlLmV4aXN0cyhzY2VfZmlsZSksCiAgImF1Y2VsbCByZXN1bHRzIGZpbGUgZG9lcyBub3QgZXhpc3QiID0gZmlsZS5leGlzdHMoYXVjZWxsX3Jlc3VsdHNfZmlsZSkKKQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyByZWFkIGluIHNjZQpzY2UgPC0gcmVhZHI6OnJlYWRfcmRzKHNjZV9maWxlKQoKIyByZWFkIGluIHdvcmtmbG93IHJlc3VsdHMKc2luZ2xlcl9kZiA8LSBzaW5nbGVyX3Jlc3VsdHNfZmlsZXMgfD4gCiAgcHVycnI6OnNldF9uYW1lcyhsaWJyYXJ5X2lkcykgfD4KICBwdXJycjo6bWFwKHJlYWRyOjpyZWFkX3RzdikgfD4gCiAgZHBseXI6OmJpbmRfcm93cyguaWQgPSAibGlicmFyeV9pZCIpCgphdWNlbGxfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KGF1Y2VsbF9yZXN1bHRzX2ZpbGUpIHw+IAogIHRpZHlyOjpzZXBhcmF0ZShiYXJjb2RlcywgIi0iLCBpbnRvID0gYygibGlicmFyeV9pZCIsICJiYXJjb2RlcyIpKQoKIyBjb25zZW5zdXMgY2VsbCB0eXBlcyAKY29uc2Vuc3VzX2RmIDwtIGNvbnNlbnN1c19yZXN1bHRzX2ZpbGVzIHw+IAogIHB1cnJyOjptYXAocmVhZHI6OnJlYWRfdHN2KSB8PiAKICBkcGx5cjo6YmluZF9yb3dzKCkKCiMgcmVhZCBpbiBtYXJrZXIgZ2VuZXMgYW5kIGNvbWJpbmUgaW50byBvbmUgbGlzdCAKdmlzc2VyX21hcmtlcnNfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHZpc3Nlcl9tYXJrZXJfZ2VuZXNfZmlsZSkgfD4gCiAgZHBseXI6OnNlbGVjdChjZWxsX3R5cGUsIGVuc2VtYmxfZ2VuZV9pZCwgZ2VuZV9zeW1ib2wpIHw+IAogIHVuaXF1ZSgpCiAgCmNlbGxfc3RhdGVfbWFya2Vyc19kZiA8LSByZWFkcjo6cmVhZF90c3YoY2VsbF9zdGF0ZV9nZW5lc19maWxlKSB8PiAKICBkcGx5cjo6c2VsZWN0KGNlbGxfdHlwZSA9IGNlbGxfc3RhdGUsIGVuc2VtYmxfZ2VuZV9pZCwgZ2VuZV9zeW1ib2wpCgphbGxfbWFya2Vyc19kZiA8LSBkcGx5cjo6YmluZF9yb3dzKGxpc3Qodmlzc2VyX21hcmtlcnNfZGYsIGNlbGxfc3RhdGVfbWFya2Vyc19kZikpCmBgYAoKIyMgUHJlcGFyZSBkYXRhIGZvciBwbG90dGluZwoKYGBge3J9CmFsbF9yZXN1bHRzX2RmIDwtIHByZXBfcmVzdWx0cygKICBzY2UsIAogIHNpbmdsZXJfZGYgPSBzaW5nbGVyX2RmLCAKICBjbHVzdGVyX2RmID0gTlVMTCwgCiAgYXVjZWxsX2RmID0gYXVjZWxsX2RmLAogIGNvbnNlbnN1c19kZiA9IGNvbnNlbnN1c19kZiwKICBjbHVzdGVyX25uID0gcGFyYW1zJGNsdXN0ZXJfbm4sCiAgY2x1c3Rlcl9yZXMgPSBwYXJhbXMkY2x1c3Rlcl9yZXMsCiAgam9pbl9jb2x1bW5zID0gYygiYmFyY29kZXMiLCAibGlicmFyeV9pZCIpCiAgKSB8PgogIGRwbHlyOjptdXRhdGUoYmFyY29kZXMgPSBnbHVlOjpnbHVlKCJ7bGlicmFyeV9pZH0te2JhcmNvZGVzfSIpKQogIApjZWxsX3R5cGVzIDwtIHVuaXF1ZShhbGxfbWFya2Vyc19kZiRjZWxsX3R5cGUpCgojIGdldCB0aGUgbWVhbiBleHByZXNzaW9uIG9mIGFsbCBnZW5lcyBmb3IgZWFjaCBjZWxsIHN0YXRlCmdlbmVfZXhwX2RmIDwtIGNlbGxfdHlwZXMgfD4KICBwdXJycjo6bWFwKFwodHlwZSl7CiAgICBjYWxjdWxhdGVfbWVhbl9tYXJrZXJzKGFsbF9tYXJrZXJzX2RmLCBzY2UsIHR5cGUsIGNlbGxfdHlwZSkKICB9KSB8PgogIHB1cnJyOjpyZWR1Y2UoZHBseXI6OmlubmVyX2pvaW4sIGJ5ID0gImJhcmNvZGVzIikKCmFsbF9pbmZvX2RmIDwtIGFsbF9yZXN1bHRzX2RmIHw+IAogIGRwbHlyOjpsZWZ0X2pvaW4oZ2VuZV9leHBfZGYsIGJ5ID0gImJhcmNvZGVzIikKYGBgCgojIyBSZXN1bHRzIHN1bW1hcnkKCkZpcnN0LCB3ZSB3aWxsIGp1c3Qgc3VtbWFyaXplIHRoZSByZXN1bHRzIGZyb20gdGhlIHZhcmlvdXMgaW5wdXRzIGFuZCB3b3JrZmxvd3MuIApUaGVuIHdlIHdpbGwgbG9vayBhdCB0aG9zZSByZXN1bHRzIGFuZCBhc3NpZ24gYW55IHR1bW9yIGNlbGxzIGFuZCByZWZpbmUgYW5ub3RhdGlvbnMgYXMgbmVlZGVkLiAKCiMjIyBgYXVjZWxsLXNpbmdsZXItYW5ub3RhdGlvbmAgYXNzaWdubWVudHMgCgpUaGUgYmVsb3cgVU1BUCBzaG93cyB0aGUgdG9wIGNlbGwgdHlwZXMgYXNzaWduZWQgYnkgYFNpbmdsZVJgIGluIHRoZSBgYXVjZWxsLXNpbmdsZXItYW5ub3RhdGlvbi5zaGAgd29ya2Zsb3cuIApUaGUgdG9wIDcgY2VsbCB0eXBlcyBhcmUgc2hvd24gYW5kIGFsbCBvdGhlciBjZWxsIHR5cGVzIGFyZSBncm91cGVkIHRvZ2V0aGVyIGFzICJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLiAKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNX0KcGxvdF9mYWNldGVkX3VtYXAoYWxsX2luZm9fZGYsIHNpbmdsZXJfbHVtcGVkLCBsZWdlbmRfdGl0bGUgPSAiU2luZ2xlUiBjZWxsIHR5cGVzIikgKwogIHRoZW1lKHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKQpgYGAKCgojIyMgQ29uc2Vuc3VzIGNlbGwgdHlwZSBhc3NpZ25tZW50cyAKClRoZSBiZWxvdyBVTUFQIHNob3dzIHRoZSB0b3AgY2VsbCB0eXBlcyBmb3Igd2hpY2ggY29uc2Vuc3VzIGNlbGwgdHlwZXMgd2VyZSBhc3NpZ25lZC4KQ29uc2Vuc3VzIGNlbGwgdHlwZXMgYXJlIG9ic2VydmVkIHdoZW4gY2VsbCB0eXBlcyBmcm9tIGBTaW5nbGVSYCBhbmQgYENlbGxBc3NpZ25gIHNoYXJlIGEgY29tbW9uIGFuY2VzdG9yLiAKSWYgbm8gY29uc2Vuc3VzIGlzIGZvdW5kLCB0aGUgY2VsbHMgYXJlIGxhYmVsZWQgd2l0aCAiVW5rbm93biIuIApXZSBhbnRpY2lwYXRlIHRoYXQgaW4gbWFueSBjYXNlcywgdGhlICJVbmtub3duIiBjZWxscyB3aWxsIGJlIHR1bW9yIGNlbGxzLgpUaGUgdG9wIDcgY2VsbCB0eXBlcyBhcmUgc2hvd24gYW5kIGFsbCBvdGhlciBjZWxsIHR5cGVzIGFyZSBncm91cGVkIHRvZ2V0aGVyIGFzICJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLiAKCmBgYHtyLCBmaWcuaGVpZ2h0PTV9CnBsb3RfZmFjZXRlZF91bWFwKGFsbF9pbmZvX2RmLCBjb25zZW5zdXNfbHVtcGVkLCBsZWdlbmRfdGl0bGUgPSAiQ29uc2Vuc3VzIGNlbGwgdHlwZXMiKSArCiAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCmBgYAoKCiMjIyBgQVVDZWxsYCByZXN1bHRzIAoKVGhlIGJlbG93IHBsb3RzIHNob3cgdGhlIEFVQyB2YWx1ZXMgZGV0ZXJtaW5lZCBieSBgQVVDZWxsYCBhbmQgb3V0cHV0IGZyb20gYHJ1bi1hdWNlbGwtZXdzLXNpZ25hdHVyZXMuc2hgLiAKVGhlIGZpcnN0IHBsb3Qgc2hvd3MgdGhlIGluZGl2aWR1YWwgQVVDIHZhbHVlcyBvbiB0aGUgVU1BUC4gCgpGb3IgY29udGV4dDogIAoKLSBBbnkgZ2VuZSBzZXRzIGxhYmVsZWQgd2l0aCBgdXBgIHJlcHJlc2VudCBgRVdTLUZMSTFgIHRhcmdldCBnZW5lcyB0aGF0IHdlIGV4cGVjdCB0byBiZSB1cHJlZ3VsYXRlZCBpbiBgRVdTLUZMSTFgIGhpZ2ggdHVtb3IgY2VsbHMuIAotIEFueSBnZW5lIHNldHMgbGFiZWxlZCB3aXRoIGBkb3duYCByZXByZXNlbnQgYEVXUy1GTEkxYCByZXByZXNzZWQgZ2VuZXMgdGhhdCB3ZSBleHBlY3QgdG8gYmUgZG93bnJlZ3VsYXRlZCBpbiBgRVdTLUZMSTFgIGhpZ2ggdHVtb3IgY2VsbHMgYW5kIHVwcmVndWxhdGVkIGluIGBFV1MtRkxJMWAgbG93IHR1bW9yIGNlbGxzLiAKLSBUaGUgYGF5bmF1ZC1ld3MtdGFyZ2V0c2AgaXMgYSBsaXN0IG9mIGdlbmVzIGZvdW5kIHRvIGJlIHVwcmVndWxhdGVkIGluIGBFV1MtRkxJMWAgaGlnaCB0dW1vciBjZWxscy4gCi0gVGhlIGB3cmVubi1udDVlLWdlbmVzYCBpcyBhIGxpc3Qgb2YgZ2VuZXMgZm91bmQgdG8gYmUgdXByZWd1bGF0ZWQgaW4gYEVXUy1GTEkxYCBsb3cgdHVtb3IgY2VsbHMvIGNhbmNlciBhc3NvY2lhdGVkIGZpYnJvYmxhc3RzLgotIFRoZSBgZ29icF9FQ01gIGFuZCBgaGFsbG1hcmtfRU1UYCBhcmUgZ2VuZSBzZXRzIHRoYXQgYXJlIGh5cG90aGVzaXplZCB0byBiZSB1cHJlZ3VsYXRlZCBpbiBgRVdTLUZMSTFgIGxvdyB0dW1vciBjZWxscy8gY2FuY2VyIGFzc29jaWF0ZWQgZmlicm9ibGFzdHMuCgpgYGB7cn0KIyBnZXQgdGhlIGluZGl2aWR1YWwgdGhyZXNob2xkcyBkZXRlcm1pbmVkIGJ5IEFVQ2VsbCBmb3IgZWFjaCBtc2lnZGIgZ2VuZXNldAojIHdlIHdhbnQgdGhpcyBzbyB3ZSBjYW4gc2hvdyB0aGUgdGhyZXNob2xkIG9uIHRoZSBkZW5zaXR5IHBsb3RzIAphdWNfdGhyZXNob2xkX2RmIDwtIGFsbF9pbmZvX2RmIHw+IAogIHRpZHlyOjpwaXZvdF9sb25nZXIoc3RhcnRzX3dpdGgoInRocmVzaG9sZF9hdWNfIiksIG5hbWVzX3RvID0gImdlbmVzZXQiLCB2YWx1ZXNfdG8gPSAidGhyZXNob2xkIikgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgIGdlbmVzZXQgPSBzdHJpbmdyOjpzdHJfcmVtb3ZlKGdlbmVzZXQsICJ0aHJlc2hvbGRfYXVjXyIpCiAgKSB8PiAKICBkcGx5cjo6c2VsZWN0KGJhcmNvZGVzLCBnZW5lc2V0LCB0aHJlc2hvbGQpCgojIHJlZm9ybWF0IGF1YyBkYXRhIGZvciBkZW5zaXR5IHBsb3RzIGFuZCBVTUFQcyBzaG93aW5nIEFVQyB2YWx1ZXMgCmF1Y19kZiA8LSBhbGxfaW5mb19kZiB8PiAKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKHN0YXJ0c193aXRoKCJhdWNfIiksIG5hbWVzX3RvID0gImdlbmVzZXQiLCB2YWx1ZXNfdG8gPSAiYXVjX3ZhbHVlIikgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgIGdlbmVzZXQgPSBzdHJpbmdyOjpzdHJfcmVtb3ZlKGdlbmVzZXQsICJhdWNfIikKICApIHw+IAogIGRwbHlyOjpzZWxlY3QoYmFyY29kZXMsIFVNQVAxLCBVTUFQMiwgZ2VuZXNldCwgYXVjX3ZhbHVlKSB8PiAKICBkcGx5cjo6bGVmdF9qb2luKGF1Y190aHJlc2hvbGRfZGYsIGJ5ID0gYygiYmFyY29kZXMiLCAiZ2VuZXNldCIpKSB8PiAKICBkcGx5cjo6bXV0YXRlKAogICAgaW5fZ2VuZXNldCA9ICBhdWNfdmFsdWUgPiB0aHJlc2hvbGQKICApCmBgYAoKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CmV4cHJlc3Npb25fdW1hcChhdWNfZGYsIGF1Y192YWx1ZSwgZ2VuZXNldCkKYGBgCgpUaGUgYmVsb3cgcGxvdCBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIEFVQyB2YWx1ZXMgZm9yIGVhY2ggZ2VuZSBzZXQgY29sb3JlZCBiYXNlZCBvbiBpZiB0aGUgQVVDIHZhbHVlIGlzIGFib3ZlIHRoZSB0aHJlc2hvbGQgZGV0ZXJtaW5lZCBieSBgQVVDZWxsYCwgaW5kaWNhdGVkIHdpdGggYSBkb3R0ZWQgbGluZS4gCgoKYGBge3IsIGZpZy5oZWlnaHQ9NX0KCmdncGxvdChhdWNfZGYsIGFlcyh4ID0gYXVjX3ZhbHVlLCBjb2xvciA9IGluX2dlbmVzZXQsIGZpbGwgPSBpbl9nZW5lc2V0KSkgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSwgYncgPSAwLjAxKSArCiAgZmFjZXRfd3JhcCh2YXJzKGdlbmVzZXQpKSArCiAgZ2dwbG90Mjo6Z2VvbV92bGluZShkYXRhID0gYXVjX2RmLAogICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4aW50ZXJjZXB0ID0gdGhyZXNob2xkKSwKICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDIpICsKICB0aGVtZSgKICAgICAgYXNwZWN0LnJhdGlvID0gMSwKICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgbGluZXdpZHRoID0gMC41KSwKICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG9yID0gImJsYWNrIiwgZmlsbCA9IE5BLCBsaW5ld2lkdGggPSAwLjUpCiAgICApIApgYGAKCgpIZXJlIHdlIGxvb2sgYXQgdGhlIEFVQyB2YWx1ZXMgZm9yIGVhY2ggZ2VuZSBzZXQgYWNyb3NzIGFsbCBjb25zZW5zdXMgY2VsbCB0eXBlcy4gIAoKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwfQphdWNfY29sdW1ucyA8LSBjb2xuYW1lcyhhbGxfaW5mb19kZilbd2hpY2goc3RhcnRzV2l0aChjb2xuYW1lcyhhbGxfaW5mb19kZiksICJhdWNfIikpXQpjbHVzdGVyX2RlbnNpdHlfcGxvdChhbGxfaW5mb19kZiwgYXVjX2NvbHVtbnMsICJjb25zZW5zdXNfbHVtcGVkIiwgIkFVQyIpCmBgYAoKSGVyZSB3ZSBsb29rIGF0IHRoZSBBVUMgdmFsdWVzIGZvciBlYWNoIGdlbmUgc2V0IGFjcm9zcyBhbGwgYFNpbmdsZVJgIGNlbGwgdHlwZXMuICAKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwfQpjbHVzdGVyX2RlbnNpdHlfcGxvdChhbGxfaW5mb19kZiwgYXVjX2NvbHVtbnMsICJzaW5nbGVyX2x1bXBlZCIsICJBVUMiKQpgYGAKClRoZSBoZWF0bWFwIGJlbG93IHNob3dzIHRoZSBBVUMgdmFsdWVzIGZvciBhbGwgY2VsbHMgYW5kIGFsbCBnZW5lIHNldHMgYW5kIHRoZSBmaW5hbCByZWZpbmVkIGNlbGwgdHlwZSBhbm5vdGF0aW9ucy4gCgpgYGB7cn0KIyBzb3J0IGJ5IGZpbmFsIGNlbGwgdHlwZXMgCmFsbF9pbmZvX2RmIDwtIGFsbF9pbmZvX2RmIHw+IAogIGRwbHlyOjphcnJhbmdlKGNvbnNlbnN1c19sdW1wZWQpCgpzaW5nbGVfYW5ub3RhdGlvbl9oZWF0bWFwKAogIGFsbF9pbmZvX2RmLCAKICBleHBfY29sdW1ucyA9IGF1Y19jb2x1bW5zLCAKICBjZWxsX3R5cGVfY29sdW1uID0gImNvbnNlbnN1c19sdW1wZWQiLCAKICBsZWdlbmRfdGl0bGUgPSAiQVVDIgopCmBgYAoKCgojIyMgTWVhbiBleHByZXNzaW9uIG9mIGN1c3RvbSBnZW5lIHNldHMKCkJlbG93IHdlIGxvb2sgYXQgdGhlIG1lYW4gZXhwcmVzc2lvbiBvZiBhbGwgZ2VuZXMgaW4gZWFjaCBvZiB0aGUgY3VzdG9tIGdlbmUgc2V0cyB3ZSBoYXZlIGFjcm9zcyB0aGUgY29uc2Vuc3VzIGNlbGwgdHlwZXMuIApGaXJzdCB1c2luZyBhIGRlbnNpdHkgcGxvdCBhbmQgdGhlbiB1c2luZyBhIGhlYXRtYXAuIAoKYGBge3IsIGZpZy5oZWlnaHQ9Nywgd2FybmluZz1GQUxTRX0KbWVhbl9leHBfY29sdW1ucyA8LSBjb2xuYW1lcyhhbGxfaW5mb19kZilbd2hpY2goZW5kc1dpdGgoY29sbmFtZXMoYWxsX2luZm9fZGYpLCAiX21lYW4iKSldCmNsdXN0ZXJfZGVuc2l0eV9wbG90KGFsbF9pbmZvX2RmLCBtZWFuX2V4cF9jb2x1bW5zLCBhbm5vdGF0aW9uX2NvbHVtbiA9ICJjb25zZW5zdXNfbHVtcGVkIiwgIm1lYW4gZ2VuZSBleHByZXNzaW9uIikKYGBgCmBgYHtyfQpzaW5nbGVfYW5ub3RhdGlvbl9oZWF0bWFwKAogIGFsbF9pbmZvX2RmLCAKICBleHBfY29sdW1ucyA9IG1lYW5fZXhwX2NvbHVtbnMsIAogIGNlbGxfdHlwZV9jb2x1bW4gPSAiY29uc2Vuc3VzX2x1bXBlZCIsIAogIGxlZ2VuZF90aXRsZSA9ICJBVUMiCikKYGBgCgojIyMgQ29uY2x1c2lvbnMgYmFzZWQgb24gd29ya2Zsb3cgcmVzdWx0cyAKCkxvb2tpbmcgYXQgdGhlc2UgcmVzdWx0cywgaXQgbG9va3MgbGlrZSB0aGluZ3MgbGFiZWxlZCBhcyAidHVtb3IiIGJ5IGBTaW5nbGVSYCBhbmQgIlVua25vd24iIGluIHRoZSBjb25zZW5zdXMgY2VsbCB0eXBlcyBoYXZlIGhpZ2ggQVVDIHZhbHVlcyBmb3IgdGhlIGBFV1MtRkxJMWAgdXByZWd1bGF0ZWQgZ2VuZSBzZXRzLiAKV2UgYWxzbyBzZWUgbG93IGV4cHJlc3Npb24gb2YgYEVXUy1GTEkxYCByZXByZXNzZWQgdGFyZ2V0cyBpbiB0aGVzZSBjZWxscy4gCkl0IGxvb2tzIGxpa2UgdGhlIGNlbGxzIHRoYXQgYXJlIGNhbGxlZCBhcyBtdXNjbGUgY2VsbHMgaW4gYm90aCBjZWxsIHR5cGVzIGhhdmUgaGlnaCBleHByZXNzaW9uIG9mIGBFV1MtRkxJMWAgdGFyZ2V0cy4gClRoaXMgbWFrZXMgbWUgdGhpbmsgdGhvc2UgYXJlIGFjdHVhbGx5IHR1bW9yIGNlbGxzIHRoYXQgYXJlIG1pcy1jbGFzc2lmaWVkLgoKSW50cmlndWluZ2x5IHdlIHNlZSBoaWdoIGV4cHJlc3Npb24gb2YgYGhhbGxtYXJrX0VNVGAgYW5kIHRoZSBgRVdTLUZMSTFgIHJlcHJlc3NlZCB0YXJnZXRzIGluIGZpYnJvYmxhc3RzLCB3aGljaCBhbHNvIGhhcHBlbiB0byBoYXZlIGhpZ2ggZXhwcmVzc2lvbiBvZiB0aGUgYEVXUy1GTEkxYCBsb3cgdGFyZ2V0cyBpZGVudGlmZWQgYnkgV3Jlbm4gX2V0IGFsLl8gKGB3cmVubi1udDVlLWdlbmVzYCkuIApUaGF0IHBhcGVyIGlkZW50aWZpZXMgYEVXUy1GTEkxYCBsb3cgY2VsbHMgYXMgY2FuY2VyIGFzc29jaWF0ZWQgZmlicm9ibGFzdHMuCkl0J3MgYWxzbyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IG1hbnkgb2YgdGhlIGdlbmVzIHRoYXQgZGVmaW5lIHRoZSBgRVdTLUZMSTFgIGxvdyBzdGF0ZSBhcmUgYWxzbyBoaWdoIGluIGZpYnJvYmxhc3RzLCBzbyBkaXN0aW5ndWlzaGluZyB0aGVtIG1heSBiZSBkaWZmaWN1bHQuIApXZSBhbHNvIHNlZSB0aGF0IHRoZSBjaG9uZHJvY3l0ZXMgaWRlbnRpZmllZCBpbiBgU2luZ2xlUmAgaGF2ZSBoaWdoIGV4cHJlc3Npb24gb2YgdGhpcyBnZW5lIHNldC4gCgpBbm90aGVyIGtleSBvYnNlcnZhdGlvbiBpcyB0aGF0IHdlIHNlZSBzZXBhcmF0aW9uIGJldHdlZW4gdGhlIEFVQyB2YWx1ZXMgb2JzZXJ2ZWQgaW4gdHVtb3IvIG90aGVyIGNlbGxzIGluIHRoZSBkZW5zaXR5IHBsb3RzLCBidXQgdGhlc2UgdmFsdWVzIGFyZSBtdWNoIGxvd2VyIHRoYW4gdGhlIEFVQyB0aHJlc2hvbGQgYXV0b21hdGljYWxseSBkZXRlcm1pbmVkIGJ5IGBBVUNlbGxgLiAKSSB0aGluayBiZWNhdXNlIG9mIHRoYXQgd2Ugd2FudCB0byBwaWNrIGdlbmUgc2V0cyB3aGVyZSB3ZSBzZWUgdmVyeSBjbGVhciBzZXBhcmF0aW9uIGluIEFVQyB2YWx1ZXMgYmV0d2VlbiBjZWxsIHR5cGVzIHRvIGFpZCBpbiBmaW5hbGl6aW5nIG91ciBhc3NpZ25tZW50cy4gCgpUaGUgZGVuc2l0eSBwbG90cyBhbHNvIHNob3cgaW5jcmVhc2VkIGV4cHJlc3Npb24gaW4gdGhlIGltbXVuZSBtYXJrZXJzIGluIGltbXVuZSBjZWxsIHBvcHVsYXRpb25zIGFuZCBlbmRvdGhlbGlhbCBjZWxsIG1hcmtlcnMgaW4gdGhlIGVuZG90aGVsaWFsIGNlbGxzLiAKVGhlcmUgbWF5IGJlIGEgc21hbGwgZ3JvdXAgb2YgY2VsbHMgdGhhdCBoYXZlIGhpZ2ggZXhwcmVzc2lvbiBvZiB0aGUgcHJvbGlmZXJhdGl2ZSBtYXJrZXJzLiAKUGVyaGFwcyB3ZSBjYW4gbGFiZWwgYW55IGNlbGxzIHRoYXQgYXJlIHR1bW9yIGNlbGxzIGFuZCBzaG93IHByb2xpZmVyYXRpdmUgbWFya2VycyBhcyBgVHVtb3IgRVdTLWhpZ2ggcHJvbGlmZXJhdGl2ZWAuIAoKIyMgUmVmaW5pbmcgY2VsbCB0eXBlIGFubm90YXRpb25zCgpIZXJlIHdlIHdpbGwgY29tYmluZSBhbm5vdGF0aW9ucyBmcm9tIGBTaW5nbGVSYCBhbmQgY29uc2Vuc3VzIGNlbGwgdHlwZXMgd2l0aCBoZWxwIGZyb20gdGhlIGBBVUNlbGxgIG91dHB1dC4gCldlJ2xsIHVzZSB0aGUgZm9sbG93aW5nIGNyaXRlcmlhOiAKCi0gYFR1bW9yIEVXUy1sb3cgQ0FGYDogSGlnaCBleHByZXNzaW9uIG9mIGB3cmVubi1udDVlLWdlbmVzYCAoPiAwLjEgQVVDKSBhbmQgaGlnaCBleHByZXNzaW9uIG9mIHJlcHJlc3NlZCB0YXJnZXRzIHVzaW5nIGBtaXlhZ2F3YV9kb3duYCAoPiAwLjAzIEFVQykKLSBgVHVtb3IgRVdTLWhpZ2hgOiBIaWdoIGV4cHJlc3Npb24gb2YgYGF5bmF1ZC1ld3MtdGFyZ2V0c2AgKD4wLjAyNSBBVUMpIGFuZCBpZGVudGlmaWVkIGFzIHR1bW9yIHdpdGggYFNpbmdsZVJgIGFuZCBpZGVudGlmaWVkIGFzIGVpdGhlciB1bmtub3duLCBtdXNjbGUgY2VsbCwgb3Igc21vb3RoIG11c2NsZSBjZWxsIGluIHRoZSBjb25zZW5zdXMgY2VsbCB0eXBlcwotIGBUdW1vciBFV1MtaGlnaCBwcm9saWZlcmF0aXZlYDogQ2VsbHMgdGhhdCBtZWV0IHRoZSByZXF1aXJlbWVudHMgZm9yIGBUdW1vciBFV1MtaGlnaGAgYW5kIGhhdmUgZXhwcmVzc2lvbiBvZiBwcm9saWZlcmF0aXZlIG1hcmtlcnMgZGVmaW5lZCBhcyBtZWFuID4gMAotIEFsbCBvdGhlciBjZWxsIHR5cGVzIHdpbGwgYmUgYmFzZWQgb24gdGhlIGBjb25zZW5zdXNfYW5ub3RhdGlvbmAKCmBgYHtyfQojIHNldCBkZXNpcmVkIG9yZGVyIGZvciBwbG90dGluZyBmaW5hbCBhbm5vdGF0aW9ucyAKY2VsbF90eXBlX29yZGVyIDwtIGMoCiAgInR1bW9yIEVXUy1oaWdoIiwKICAidHVtb3IgRVdTLWhpZ2ggcHJvbGlmZXJhdGl2ZSIsCiAgInR1bW9yIEVXUy1sb3cgQ0FGIiwKICAiZmlicm9ibGFzdCIsCiAgImVuZG90aGVsaWFsIGNlbGwiLAogICJtYWNyb3BoYWdlIiwKICAibWF0dXJlIFQgY2VsbCIsCiAgIm1lbW9yeSBUIGNlbGwiLAogICJVbmtub3duIiwKICAiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIgopCgojIGRlZmluZSAiZmluYWwuZmluYWwiIGNlbGwgdHlwZXMgCmFsbF9pbmZvX2RmIDwtIGFsbF9pbmZvX2RmIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICBmaW5hbF9hbm5vdGF0aW9uID0gZHBseXI6OmNhc2Vfd2hlbigKICAgICAgYGF1Y193cmVubi1udDVlLWdlbmVzYCA+IDAuMSAmIGF1Y19taXlhZ2F3YV9kb3duID4gMC4wMyB+ICJ0dW1vciBFV1MtbG93IENBRiIsCiAgICAgIGBhdWNfYXluYXVkLWV3cy10YXJnZXRzYCA+IDAuMDI1IHwgCiAgICAgICAgY29uc2Vuc3VzX2Fubm90YXRpb24gJWluJSBjKCJVbmtub3duIiwgIm11c2NsZSBjZWxsIiwgInNtb290aCBtdXNjbGUgY2VsbCIpICYKICAgICAgICBzaW5nbGVyX2x1bXBlZCA9PSAidHVtb3IiIH4gInR1bW9yIEVXUy1oaWdoIiwKICAgICAgLmRlZmF1bHQgPSBjb25zZW5zdXNfYW5ub3RhdGlvbgogICAgKSwKICAgIGZpbmFsX2Fubm90YXRpb24gPSBkcGx5cjo6aWZfZWxzZSgKICAgICAgZmluYWxfYW5ub3RhdGlvbiA9PSAidHVtb3IgRVdTLWhpZ2giICYgcHJvbGlmZXJhdGl2ZV9tZWFuID4gMCwKICAgICAgInR1bW9yIEVXUy1oaWdoIHByb2xpZmVyYXRpdmUiLAogICAgICBmaW5hbF9hbm5vdGF0aW9uCiAgICApLAogICAgIyBsdW1wIHRvZ2V0aGVyIGZvciBlYXNpZXIgcGxvdHRpbmcKICAgIGZpbmFsX2x1bXBlZCA9IGZpbmFsX2Fubm90YXRpb24gfD4KICAgICAgZm9yY2F0czo6ZmN0X2x1bXBfbig5LCBvdGhlcl9sZXZlbCA9ICJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpIHw+CiAgICAgIGZvcmNhdHM6OmZjdF9pbmZyZXEoKSB8PgogICAgICBmb3JjYXRzOjpmY3RfcmVsZXZlbChjZWxsX3R5cGVfb3JkZXIpCiAgKQoKYWxsX2luZm9fZGYgfD4gCiAgZHBseXI6OmNvdW50KGZpbmFsX2Fubm90YXRpb24pIHw+IAogIGRwbHlyOjphcnJhbmdlKGRlc2MobikpCmBgYApGb3IgcGxvdHRpbmcgcHVycG9zZXMsIHdlIHdvbid0IHNob3cgYW55IG9mIHRoZSBjZWxsIHR5cGVzIHRoYXQgb25seSBoYXZlIGEgaGFuZGZ1bCBvZiBjZWxscy4gCkxldCdzIHNlZSB3aGF0IGNlbGwgdHlwZXMgd2Ugd29uJ3QgYmUgbG9va2luZyBhdCBpZiB3ZSBvbmx5IHBsb3QgdGhlIHRvcCA5IGNlbGwgdHlwZXMuIAoKYGBge3J9Cmx1bXBlZF9jZWxsdHlwZXMgPC0gdW5pcXVlKGFsbF9pbmZvX2RmJGZpbmFsX2x1bXBlZCkKYWxsX2NlbGx0eXBlcyA8LSB1bmlxdWUoYWxsX2luZm9fZGYkZmluYWxfYW5ub3RhdGlvbikKbWlzc2luZ19jZWxsdHlwZXMgPC0gc2V0ZGlmZihhbGxfY2VsbHR5cGVzLCBsdW1wZWRfY2VsbHR5cGVzKQoKbWlzc2luZ19vbmx5IDwtIGFsbF9pbmZvX2RmIHw+IAogIGRwbHlyOjpmaWx0ZXIoZmluYWxfYW5ub3RhdGlvbiAlaW4lIG1pc3NpbmdfY2VsbHR5cGVzKQoKbWlzc2luZ19vbmx5IHw+IAogIGRwbHlyOjpjb3VudChmaW5hbF9hbm5vdGF0aW9uKSB8PiAKICBkcGx5cjo6YXJyYW5nZShkZXNjKG4pKQpgYGAKCkkgdGhpbmsgd2UgY2FuIGxlYXZlIHRoZXNlIGZldyBjZWxscyBhbG9uZSBhbmQganVzdCBsdW1wIHRoZW0gYXMgIkFsbCByZW1haW5pbmcgY2VsbCB0eXBlcyIgZm9yIHBsb3RzIGFzIHdlIGhhdmUgYmVlbiBkb2luZy4gCgojIyBWYWxpZGF0aW9uIG9mIGNlbGwgdHlwZSBhc3NpZ25tZW50cwoKSW4gdGhlIGZvbGxvd2luZyBzZWN0aW9uIHdlJ2xsIHNob3cgc29tZSBwbG90cyB0byBoZWxwIHZhbGlkYXRlIHRoYXQgd2UgYXJlIGNvbnRlbnQgd2l0aCBvdXIgY2VsbCB0eXBlIGFzc2lnbm1lbnRzLiAKVGhlc2UgcGxvdHMgd2lsbCBsb29rIHNwZWNpZmljYWxseSBhdCBleHByZXNzaW9uIG9mIGEgc2V0IG9mIGtleSBtYXJrZXIgZ2VuZXMgdGhhdCB3ZSBleHBlY3QgdG8gdXAgaW4gdGhlIGFzc2lnbmVkIGNlbGwgdHlwZXMuIApUaGVzZSBtYXJrZXJzIGFyZSBmb3VuZCBpbiBgcmVmZXJlbmNlcy9jb21iaW5lZC1tYXJrZXJzLnRzdmAgYW5kIGluY2x1ZGUgYSBzbWFsbGVyIHNldCBvZiBtYXJrZXJzIGZvciBlYWNoIG9mIHRoZSBjZWxsIHR5cGVzIHdlIGhhdmUgaWRlbnRpZmllZC4gCgpgYGB7cn0KdmFsaWRhdGlvbl9tYXJrZXJzX2RmIDwtIHJlYWRyOjpyZWFkX3Rzdih2YWxpZGF0aW9uX21hcmtlcnNfZmlsZSkKCiMgcHVsbCBvdXQgbGlzdCBvZiBnZW5lcyAKZ2VuZXMgPC0gdmFsaWRhdGlvbl9tYXJrZXJzX2RmIHw+CiAgZHBseXI6OnB1bGwoZW5zZW1ibF9nZW5lX2lkKQoKIyBnZXQgaW5kaXZpZHVhbCBnZW5lIGNvdW50cyBmb3IgYWxsIG1hcmtlciBnZW5lcyAKZ2VuZV9jdHMgPC0gbG9nY291bnRzKHNjZVtnZW5lcywgXSkgfD4KICBhcy5tYXRyaXgoKSB8PgogIHQoKSB8PiAKICBhcy5kYXRhLmZyYW1lKCkKZ2VuZV9jdHMkYmFyY29kZXMgPC0gcm93bmFtZXMoZ2VuZV9jdHMpCgojIGdldCBhbGwgdW5pcXVlIGNlbGwgdHlwZXMgZnJvbSB0aGUgZmluYWwgbHVtcGVkIGdyb3VwIApjZWxsdHlwZXNfZGYgPC0gYWxsX2luZm9fZGYgfD4gCiAgZHBseXI6OnNlbGVjdChiYXJjb2RlcywgZmluYWxfbHVtcGVkKQoKIyBjcmVhdGUgYSBkZiB0aGF0IGhhcyBnZW5lIGV4cHJlc3Npb24gY29sdW1uIGFuZCBjb2x1bW4gaW5kaWNhdGluZyB3aGV0aGVyIG9yIG5vdCB0aGUgZ2VuZSBpcyBkZXRlY3RlZCBpbiB0aGF0IGNlbGwgCmdlbmVzX2RmIDwtIGdlbmVfY3RzIHw+IAogIHRpZHlyOjpwaXZvdF9sb25nZXIoIWJhcmNvZGVzLCBuYW1lc190byA9ICJlbnNlbWJsX2dlbmVfaWQiLCB2YWx1ZXNfdG8gPSAiZ2VuZV9leHAiKSB8PiAKICBkcGx5cjo6bGVmdF9qb2luKGNlbGx0eXBlc19kZiwgYnkgPSBjKCJiYXJjb2RlcyIpKSB8PiAKICBkcGx5cjo6bGVmdF9qb2luKHZhbGlkYXRpb25fbWFya2Vyc19kZiwgYnkgPSBjKCJlbnNlbWJsX2dlbmVfaWQiKSkgfD4gCiAgZHBseXI6OnJvd3dpc2UoKSB8PiAKICBkcGx5cjo6bXV0YXRlKAogICAgZGV0ZWN0ZWQgPSBnZW5lX2V4cCA+IDAgIyBjb2x1bW4gaW5kaWNhdGluZyBpZiBnZW5lIGlzIHByZXNlbnQgb3Igbm90CiAgKQoKIyBnZXQgdG90YWwgbnVtYmVyIG9mIGNlbGxzIHBlciBmaW5hbCBhbm5vdGF0aW9uIGdyb3VwIAp0b3RhbF9jZWxsc19kZiA8LSBnZW5lc19kZiB8PiAKICBkcGx5cjo6c2VsZWN0KGJhcmNvZGVzLCBmaW5hbF9sdW1wZWQpIHw+IAogIHVuaXF1ZSgpIHw+IAogIGRwbHlyOjpjb3VudChmaW5hbF9sdW1wZWQsIG5hbWUgPSAidG90YWxfY2VsbHMiKQoKIyBnZXQgdG90YWwgbnVtYmVyIG9mIGNlbGxzIGVhY2ggZ2VuZSBpcyBkZXRlY3RlZCBpbiBwZXIgZ3JvdXAgCiMgYW5kIG1lYW4gZ2VuZSBleHByZXNzaW9uIHBlciBncm91cCAKZ3JvdXBfc3RhdHNfZGYgPC0gZ2VuZXNfZGYgfD4gCiAgZHBseXI6Omdyb3VwX2J5KGZpbmFsX2x1bXBlZCwgZW5zZW1ibF9nZW5lX2lkKSB8PgogIGRwbHlyOjpzdW1tYXJpemUoCiAgICBkZXRlY3RlZF9jb3VudCA9IHN1bShkZXRlY3RlZCksCiAgICBtZWFuX2V4cCA9IG1lYW4oZ2VuZV9leHApCiAgKQoKZ2VuZV9zdW1tYXJ5X2RmIDwtIGdlbmVzX2RmIHw+IAogICMgYWRkIHRvdGFsIGNlbGxzCiAgZHBseXI6OmxlZnRfam9pbih0b3RhbF9jZWxsc19kZiwgYnkgPSBjKCJmaW5hbF9sdW1wZWQiKSkgfD4gCiAgIyBhZGQgcGVyIGdlbmUgc3RhdHMKICBkcGx5cjo6bGVmdF9qb2luKGdyb3VwX3N0YXRzX2RmLCBieSA9IGMoImVuc2VtYmxfZ2VuZV9pZCIsICJmaW5hbF9sdW1wZWQiKSkgfD4gCiAgZHBseXI6OnNlbGVjdChnZW5lX3N5bWJvbCwgZmluYWxfbHVtcGVkLCBjZWxsX3R5cGUsIHRvdGFsX2NlbGxzLCBkZXRlY3RlZF9jb3VudCwgbWVhbl9leHApIHw+IAogIHVuaXF1ZSgpIHw+CiAgZHBseXI6OnJvd3dpc2UoKSB8PiAKICBkcGx5cjo6bXV0YXRlKAogICAgIyBnZXQgdG90YWwgcGVyY2VudAogICAgcGVyY2VudF9leHAgPSAoZGV0ZWN0ZWRfY291bnQvdG90YWxfY2VsbHMpICogMTAwLAogICAgIyBvcmRlciBnZW5lcyBiYXNlZCBvbiBjZWxsIHR5cGUgdGhleSBpbmRpY2F0ZQogICAgZ2VuZV9zeW1ib2wgPSBmYWN0b3IoZ2VuZV9zeW1ib2wsIGxldmVscyA9IHZhbGlkYXRpb25fbWFya2Vyc19kZiRnZW5lX3N5bWJvbCksCiAgICBmaW5hbF9sdW1wZWQgPSBmb3JjYXRzOjpmY3RfcmVsZXZlbChmaW5hbF9sdW1wZWQsIGNlbGxfdHlwZV9vcmRlcikKICAgIAogICkgCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9N30KIyBmaWx0ZXIgb3V0IGxvdyBleHByZXNzZWQgZ2VuZXMKZG90cGxvdF9kZiA8LSBnZW5lX3N1bW1hcnlfZGYgfD4gCiAgZHBseXI6OmZpbHRlcihtZWFuX2V4cCA+IDAsIHBlcmNlbnRfZXhwID4gMTApCgoKZG90cGxvdCA8LSBnZ3Bsb3QoZG90cGxvdF9kZiwgYWVzKHkgPSBmb3JjYXRzOjpmY3RfcmV2KGZpbmFsX2x1bXBlZCksIHggPSBnZW5lX3N5bWJvbCwgY29sb3IgPSBtZWFuX2V4cCwgc2l6ZSA9IHBlcmNlbnRfZXhwKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbiA9ICJtYWdtYSIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41KQogICkgKwogIGxhYnMoCiAgICB4ID0gIiIsCiAgICB5ID0gIiIKICApCgpjb2xvcl9iYXIgPC0gZ2dwbG90KGRvdHBsb3RfZGYsIGFlcyh4ID0gZ2VuZV9zeW1ib2wsIHkgPSAxLCBmaWxsID0gY2VsbF90eXBlKSkgKyAKICBnZW9tX3RpbGUoKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAnU2V0MScpICsKICBnZ21hcDo6dGhlbWVfbm90aGluZygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKwogIGxhYnMoZmlsbCA9ICIiKQoKZG90cGxvdCArIGNvbG9yX2JhciArCiAgcGF0Y2h3b3JrOjpwbG90X2xheW91dChuY29sID0gMSwgaGVpZ2h0cyA9IGMoNCwgMC4xKSkgCmBgYAoKQSBmZXcgbm90ZXMgZnJvbSB0aGlzIHBsb3Q6IAoKLSBHZW5lcmFsbHkgdHVtb3IgbWFya2VycyBhcmUgcHJlc2VudCB0aHJvdWdob3V0LCBhbHRob3VnaCB0aGV5IGFyZSBoaWdoZXN0IGluIHR1bW9yIGNlbGxzLiAKLSBUdW1vciBFV1MgbG93IENBRnMgc2hvdyBwcmV0dHkgc3Ryb25nIGV4cHJlc3Npb24gb2YgdGhlIGBFV1MtbG93YCBtYXJrZXJzLiAKVGhlc2UgbWFya2VycyBhcmUgYWxzbyBwcmVzZW50IGluIGZpYnJvYmxhc3RzLCBidXQgdG8gYSBsZXNzZXIgZGVncmVlLiAKQWRkaXRpb25hbGx5LCBgVE5DYCAod2hpY2ggaGFzIGJlZW4gcHVibGlzaGVkIHRvIGJlIGltcG9ydGFudCBpbiB0aGUgbWV0YXN0YXRpYyBwaGVub3R5cGUgaW4gRXdpbmcgY2VsbHMpIGlzIHNwZWNpZmljIHRvIHRoZSBsb3cgY2VsbHMgYW5kIG5vdCBmb3VuZCBpbiB0aGUgZmlicm9ibGFzdHMsIHdoaWNoIG1ha2VzIG1lIG1vcmUgY29uZmlkZW50IHRoYXQgdGhvc2UgYXJlIGluZGVlZCB0dW1vciBjZWxscy4gCi0gRW5kb3RoZWxpYWwgY2VsbHMgc2hvdyBzdHJvbmcgZXhwcmVzc2lvbiBvZiBgUEVDQU0xYCBhbmQgYFZXRmAsIHdoaWxlIHRoYXQgaXMgbm90IHNlZW4gaW4gbW9zdCBvZiB0aGUgb3RoZXIgY2VsbHMuIAotIEFsbCBpbW11bmUgY2VsbCB0eXBlcyBzaG93IGBQVFBSQ2AgYXMgZXhwZWN0ZWQgd2l0aCBtYWNyb3BoYWdlcyBhbHNvIHNob3dpbmcgYE1SQzFgLiAKVGhlIG9ubHkgY29uY2VybiBJIHNlZSBpcyB0aGF0IHRoZSBgbWF0dXJlIFQgY2VsbGAgZG9lcyBub3QgZXhwcmVzcyBlaXRoZXIgb2YgdGhlIGBDRDNgIGdlbmVzLCBidXQgdGhlIG1lbW9yeSBUIGNlbGxzIGRvPyAKSSdtIG5vdCB0b3RhbGx5IHN1cmUgd2hhdCB0byBkbyB0aGVyZSBzaW5jZSB0aGF0IGxhYmVsIGlzIGEgY29uc2Vuc3VzIGxhYmVsIHNvIHBlcmhhcHMgdGhhdCBnZW5lIGlzIG5vdCBhcyB3ZWxsIGNvdmVyZWQgaW4gdGhlc2Ugc2FtcGxlcz8gCgoKTGV0J3MgbWFrZSBhIGhlYXRtYXAgdmVyc2lvbiBvZiB0aGUgc2FtZSBwbG90LiAKCmBgYHtyLCBmaWcud2lkdGg9N30KIyBub3RlIHRoYXQgd2UgY2FuJ3QgdXNlIHRoZSBzYW1lIGhlYXRtYXAgZnVuY3Rpb24gYXMgYmVmb3JlIHNpbmNlIHdlIGFyZSBsb29raW5nIGF0IGNlbGwgdHlwZXMgYXMgcm93cyByYXRoZXIgdGhhbiBnZW5lIHNldHMKIyB3ZSBhbHNvIHdhbnQgdG8gc3BlY2lmeSB0aGUgYW5ub3RhdGlvbiBuYW1lCgojIGZpcnN0IG1ha2UgYSBtdHggdG8gdXNlIGZvciB0aGUgaGVhdG1hcCB3aXRoIHJvd3MgYXMgY2VsbCB0eXBlcyBhbmQgZ2VuZXMgYXMgY29sdW1ucyAKaGVhdG1hcF9tdHggPC0gZ2VuZV9zdW1tYXJ5X2RmIHw+IAogIGRwbHlyOjpzZWxlY3QoZ2VuZV9zeW1ib2wsIGZpbmFsX2x1bXBlZCwgbWVhbl9leHApIHw+IAogIHRpZHlyOjpwaXZvdF93aWRlcigKICAgIG5hbWVzX2Zyb20gPSBnZW5lX3N5bWJvbCwKICAgIHZhbHVlc19mcm9tID0gbWVhbl9leHAKICApIHw+IAogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJmaW5hbF9sdW1wZWQiKSB8PgogIGFzLm1hdHJpeCgpCiMgbWFrZSBzdXJlIGNlbGwgdHlwZXMgYXJlIHByZXNlbnQgaW4gdGhlIHJpZ2h0IG9yZGVyCmhlYXRtYXBfbXR4IDwtIGhlYXRtYXBfbXR4W2NlbGxfdHlwZV9vcmRlcixdCgojIGdldCBhbm5vdGF0aW9uIGNvbG9ycyBmb3IgZWFjaCBvZiB0aGUgbWFya2VyIGdlbmUgdHlwZXMgCm1hcmtlcl9nZW5lX2NhdGVnb3JpZXMgPC0gdW5pcXVlKHZhbGlkYXRpb25fbWFya2Vyc19kZiRjZWxsX3R5cGUpCm51bV9jYXRlZ29yaWVzIDwtIGxlbmd0aChtYXJrZXJfZ2VuZV9jYXRlZ29yaWVzKQpjYXRlZ29yeV9jb2xvcnMgPC0gcGFsZXR0ZS5jb2xvcnMocGFsZXR0ZSA9ICJEYXJrMiIpIHw+CiAgaGVhZChuID0gbnVtX2NhdGVnb3JpZXMpIHw+CiAgcHVycnI6OnNldF9uYW1lcyhtYXJrZXJfZ2VuZV9jYXRlZ29yaWVzKQoKCiMgY3JlYXRlIGFubm90YXRpb24gZm9yIGhlYXRtYXAKYW5ub3RhdGlvbiA8LSBDb21wbGV4SGVhdG1hcDo6Y29sdW1uQW5ub3RhdGlvbigKICBtYXJrZXJfZ2VuZV9jYXRlZ29yeSA9IHZhbGlkYXRpb25fbWFya2Vyc19kZiRjZWxsX3R5cGUsCiAgY29sID0gbGlzdCgKICAgIG1hcmtlcl9nZW5lX2NhdGVnb3J5ID0gY2F0ZWdvcnlfY29sb3JzCiAgKQopCgpDb21wbGV4SGVhdG1hcDo6SGVhdG1hcCgKICAgIGhlYXRtYXBfbXR4LAogICAgIyBzZXQgdGhlIGNvbG9yIHNjYWxlIGJhc2VkIG9uIG1pbiBhbmQgbWF4IHZhbHVlcwogICAgY29sID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoc2VxKG1pbihoZWF0bWFwX210eCksIG1heChoZWF0bWFwX210eCksIGxlbmd0aCA9IDIpLCBjb2xvcnMgPSBjKCJ3aGl0ZSIsICIjMDAyNzRDIikpLAogICAgYm9yZGVyID0gVFJVRSwKICAgICMjIFJvdyBwYXJhbWV0ZXJzCiAgICBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwKICAgIHJvd190aXRsZSA9ICIiLAogICAgcm93X3RpdGxlX3NpZGUgPSAibGVmdCIsCiAgICByb3dfbmFtZXNfc2lkZSA9ICJsZWZ0IiwKICAgIHJvd19kZW5kX3NpZGUgPSAicmlnaHQiLAogICAgcm93X25hbWVzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDEwKSwKICAgICMjIENvbHVtbiBwYXJhbWV0ZXJzCiAgICBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSwKICAgIHNob3dfY29sdW1uX25hbWVzID0gVFJVRSwKICAgIGNvbHVtbl9uYW1lc19ncCA9IGdyaWQ6OmdwYXIoZm9udHNpemUgPSA4KSwKICAgIHRvcF9hbm5vdGF0aW9uID0gYW5ub3RhdGlvbiwKICAgIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCgKICAgICAgdGl0bGUgPSAiTWVhbiBleHByZXNzaW9uIgogICAgKQogICkKYGBgCgoKQW5kIGZpbmFsbHkgd2UnbGwgbG9vayBhdCBvdXIgYW5ub3RhdGlvbnMgb24gYSBVTUFQISAKQmVjYXVzZSwgd2h5IG5vdC4gCgpgYGB7cn0KZ2dwbG90KGFsbF9pbmZvX2RmLCBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gZmluYWxfbHVtcGVkKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUsIHNpemUgPSAwLjAxKSArCiAgIyBzZXQgY29sb3IgZm9yIGFsbCByZW1haW5pbmcgY2VsbCB0eXBlcyBzaW5jZSB0aGVyZSBhcmUgbW9yZSBjb2xvcnMgdGhhbiBpbiB0aGUgcGFsZXR0ZQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbGV0dGUuY29sb3JzKHBhbGV0dGUgPSAiU2V0MSIpLCAiZ3JleTY1IiwgImdyZXk5MCIpKSArCiAgbGFicyhjb2xvciA9ICIiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxLCBzaXplID0gMS41KSkpCmBgYAoKIyMgRXhwb3J0IGNlbGwgdHlwZXMgCgpOb3cgdGhhdCB3ZSBoYXZlIG91ciBjZWxsIHR5cGVzIGxldCdzIGV4cG9ydCB0aGVtIHRvIGEgVFNWIHRvIHNhdmUgZm9yIGZ1dHVyZSB1c2UhIAoKYGBge3J9CmFubm90YXRpb25fZGYgPC0gYWxsX2luZm9fZGYgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgICMgcmVtb3ZlIGxpYnJhcnkgaWQgZnJvbSBiYXJjb2RlcyBzaW5jZSB3ZSBoYXZlIGEgbGlicmFyeSBpZCBjb2x1bW4gYWxyZWFkeSAKICAgIGJhcmNvZGVzID0gc3RyaW5ncjo6d29yZChiYXJjb2RlcywgLTEsIHNlcCA9ICItIiksCiAgICAjIGFzc2lnbiBvbnRvbG9neSBJRCB1c2luZyBjb25zZW5zdXMgb250b2xvZ3kgSUQKICAgICMgYW55dGhpbmcgd2l0aG91dCBhbiBJRCBpcyBlaXRoZXIgdW5rbm93biBvciB0dW1vciAKICAgIGZpbmFsX29udG9sb2d5ID0gZHBseXI6OmlmX2Vsc2UoCiAgICAgIGZpbmFsX2Fubm90YXRpb24gPT0gY29uc2Vuc3VzX2Fubm90YXRpb24sCiAgICAgIGNvbnNlbnN1c19vbnRvbG9neSwKICAgICAgZmluYWxfYW5ub3RhdGlvbgogICAgKQogICkgfD4gCiAgZHBseXI6OnNlbGVjdCgKICAgIGJhcmNvZGVzLAogICAgbGlicmFyeV9pZCwKICAgIHNhbXBsZV9pZCwKICAgIHNhbXBsZV90eXBlLAogICAgc2luZ2xlcl9vbnRvbG9neSwKICAgIHNpbmdsZXJfYW5ub3RhdGlvbiwKICAgIGNvbnNlbnN1c19hbm5vdGF0aW9uLAogICAgY29uc2Vuc3VzX29udG9sb2d5LAogICAgZmluYWxfYW5ub3RhdGlvbiwKICAgIGZpbmFsX29udG9sb2d5CiAgKQoKcmVhZHI6OndyaXRlX3Rzdihhbm5vdGF0aW9uX2RmLCBvdXRwdXRfZmlsZSkKYGBgCgoKIyMgU2Vzc2lvbiBpbmZvIAoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAKCg==